/**
 * @brief MCU SDRAM 驱动
 * @author 张勇 41204@qq.com
 * @date 2022-04
 */

#include "share/utils.h"
#include "mcu.h"
#include "_mcu_sdram.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "SDRAM"
#include "share/log4app.h"

// FMC-SDRAM 引脚初始化函数模板
// 注意: 此函数初始化了所有与 SDRAM 相关的引脚，但其中有一些引脚是可复用选择的(用*号标注的)，有些是根据 Bank1/Bank2 实际引脚复用选择的(用**号标注的)，需要用户拷贝这些代码并按实际情况定制相关引脚，并在 mcu_sdram_init() 前调用 !!!
static void _sdram_gpio_init() {
    GPIO_InitTypeDef gpio_init_struct;    
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;     // 推挽复用
    gpio_init_struct.Pull = GPIO_PULLUP;         // 上拉
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;    // 高速
    gpio_init_struct.Alternate = GPIO_AF12_FMC;  // 复用为 FMC
    
    // 初始化 FMC@GPIOB:    SDCKE1**     SDNE1**
    gpio_init_struct.Pin = GPIO_PIN_5 | GPIO_PIN_6;
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);
    
    // 初始化 FMC@GPIOC:    SDNWE*       SDNE0**      SDCKE0**
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3;
    HAL_GPIO_Init(GPIOC, &gpio_init_struct);

    // 初始化 FMC@GPIOD:    D2           D3           D13          D14          D15           D0            D1
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOD, &gpio_init_struct);
    
    // 初始化 FMC@GPIOE:    NBL0         NBL1         D4           D5           D6            D7            D8            D9            D10           D11           D12
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9  | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOE, &gpio_init_struct);
    
    // 初始化 FMC@GPIOF:    A0           A1           A2           A3           A4            A5            SDNRAS        A6            A7            A8            A9
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4  | GPIO_PIN_5  | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOF, &gpio_init_struct);
    
    // 初始化 FMC@GPIOG:    A10          A11          A12          BA0          BA1           SDCLK         SDNCAS
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5  | GPIO_PIN_8  | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOG, &gpio_init_struct);
    
    // 初始化 FMC@GPIOH:    SDCKE0**     SDNE0**      SDNWE*       SDNE1**      SDCKE1**      D16           D17          D18           D19           D20           D21           D22           D23
    gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7  | GPIO_PIN_8  | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOH, &gpio_init_struct);

    // 初始化 FMC@GPIOI:    D24          D25          D26           D27         NBL2          NBL3          D28          D29           D30           D31
    gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4  | GPIO_PIN_5  | GPIO_PIN_6 | GPIO_PIN_7  | GPIO_PIN_9  | GPIO_PIN_10;
    HAL_GPIO_Init(GPIOI, &gpio_init_struct);
}

// 向 SDRAM 发送命令
static std_err_t _sdram_send_cmds(SDRAM_HandleTypeDef *sdram, uint32_t bankx, const mcu_sdram_init_command_t *cmds) {
    FMC_SDRAM_CommandTypeDef command;
    command.CommandTarget = bankx;  // 目标 SDRAM 存储区域
    while(cmds->CommandMode < 8) {
        command.CommandMode = cmds->CommandMode;                        // 命令
        command.AutoRefreshNumber = cmds->AutoRefreshNumber;            // 自刷新次数
        command.ModeRegisterDefinition = cmds->ModeRegisterDefinition;  // 要写入模式寄存器的值
        if(HAL_OK != HAL_SDRAM_SendCommand(sdram, &command, 0x1000)) return STD_ERR_FAIL;
        if(cmds->PostWaitUs) unios_us_delay(cmds->PostWaitUs);  // 延时多少 us 才能执行下一条指令
        cmds++;
    } 
    return STD_ERR_OK;
}

std_err_t mcu_sdram_init(mcu_sdram_t *sdram, bool test) {
    std_err_t err;
    HAL_StatusTypeDef ret;
    uint32_t bank = (sdram->hsdram.Init.SDBank == FMC_SDRAM_BANK1) ? FMC_SDRAM_CMD_TARGET_BANK1 : FMC_SDRAM_CMD_TARGET_BANK2;
    
    // 设置 SDRAM 时序参数
    ret = HAL_SDRAM_Init(&sdram->hsdram, &sdram->timing); if(ret != HAL_OK) return STD_ERR_FAIL;

    if(sdram->initcmds) {
        // SDRAM 控制器初始化完成以后，还需要发送 SDRAM 初始化指令序列
        err = _sdram_send_cmds(&sdram->hsdram, bank, sdram->initcmds); if(err) return err;
    }

    // 设置刷新频率
    ret = HAL_SDRAM_ProgramRefreshRate(&sdram->hsdram, sdram->refresh_rate); if(ret != HAL_OK) return STD_ERR_FAIL;
    
    // SDRAM 测试
    if(test) return mcu_sdram_test(sdram);    
    return STD_ERR_OK;
}

/**
 * @brief SDRAM 底层驱动回调: 引脚配置，时钟使能等
 * @note  此函数会被 HAL_SDRAM_Init() 调用
 * @param hsdram SDRAM 句柄
 */
//void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef *sdram) {}

static std_err_t test_addr_lines(volatile uint16_t* base, uint32_t ADDR_LINES) {
    // 假设行/列地址是连续线性映射，实际需要和MCU手册对应
    for (uint32_t i = 0; i < ADDR_LINES; i++) {
        uint32_t test_addr = 1 << i;
        base[test_addr] = 0xAAAA;
        base[0] = 0x5555;
        // 检查是否互相干扰
        if (base[test_addr] != 0xAAAA) {
            loge("Address line %u stuck LOW or shorted\n", i);
            return STD_ERR_FAIL;
        }
        if (base[0] != 0x5555) {
            loge("Address line %u stuck HIGH or shorted\n", i);
            return STD_ERR_FAIL;
        }
        // 恢复
        base[test_addr] = 0;
        base[0] = 0;
    }
    return STD_ERR_OK;
}

static std_err_t test_bank_lines(volatile uint16_t* base, uint32_t ADDR_LINES, uint32_t BANK_LINES) {
    // 假设BANK线直接决定高位地址
    printf("Testing BANK Lines...\n");
    for (uint32_t i = 0; i < BANK_LINES; i++) {
        uint32_t test_bank = 1 << (ADDR_LINES + i);
        base[test_bank] = 0xAAAA;
        base[0] = 0x5555;
        if (base[test_bank] != 0xAAAA) {
            loge("BANK line %u stuck LOW or shorted\n", i);
            return STD_ERR_FAIL;
        }
        if (base[0] != 0x5555) {
            loge("BANK line %u stuck HIGH or shorted\n", i);
            return STD_ERR_FAIL;
        }
        base[test_bank] = 0;
        base[0] = 0;
    }
    return STD_ERR_OK;
}

static std_err_t test_data_lines(volatile uint16_t* base, uint32_t DATA_LINES) {
    // 只测试第一个单元的数据线
    printf("Testing Data Lines...\n");
    for (uint16_t i = 0; i < DATA_LINES; i++) {
        uint16_t pattern = 1 << i;
        base[0] = pattern;
        uint16_t val = base[0];
        if (val != pattern) {
            loge("Data line %u stuck or shorted (wrote 0x%04X, read 0x%04X)\n", i, pattern, val);
            return STD_ERR_FAIL;
        }
    }
    // Walking 0
    for (uint16_t i = 0; i < DATA_LINES; i++) {
        uint16_t pattern = ~(1 << i);
        base[0] = pattern;
        uint16_t val = base[0];
        if (val != pattern) {
            loge("Data line %u stuck or shorted (wrote 0x%04X, read 0x%04X)\n", i, pattern, val);
            return STD_ERR_FAIL;
        }
    }
    return STD_ERR_OK;
}

#define assert(x) if(!(x)) return STD_ERR_FAIL

std_err_t mcu_sdram_test_aligns(mcu_sdram_t *sdram) {
    std_err_t err;
    uint32_t offset = 0;
    uint8_t  v8 , *a8  = (uint8_t* )(sdram->base_addr);
    uint16_t v16, *a16 = (uint16_t*)(sdram->base_addr);
    uint32_t v32, *a32 = (uint32_t*)(sdram->base_addr);
    uint64_t v64, *a64 = (uint64_t*)(sdram->base_addr);
    
    a8[0] = 0x11; a8[1] = 0x22; a8[2] = 0x33; a8[3] = 0x44;
    assert(a8[0] == 0x11);    
    assert(a8[1] == 0x22);    
    assert(a8[2] == 0x33);    
    assert(a8[3] == 0x44);
    
    a16[0] = 0x1122; a16[1] = 0x3344;
    assert(a16[0] == 0x1122);
    assert(a16[1] == 0x3344);
    
    a32[0] = 0x44332211;
    assert(a32[0] == 0x44332211);
    
    a64[0] = 0x1122334455667788;    
    assert(a8[0] == 0x88);
    assert(a8[1] == 0x77);
    assert(a8[2] == 0x66);
    assert(a8[3] == 0x55);
    assert(a8[4] == 0x44);
    assert(a8[5] == 0x33);
    assert(a8[6] == 0x22);
    assert(a8[7] == 0x11);
    assert(a16[0] == 0x7788);
    assert(a16[1] == 0x5566);
    assert(a16[2] == 0x3344);
    assert(a16[3] == 0x1122);
    assert(a32[0] == 0x55667788);
    assert(a32[1] == 0x11223344);
    assert(a64[0] == 0x1122334455667788);
    
    v8  = *(uint8_t *)(sdram->base_addr + offset);
    v16 = *(uint16_t*)(sdram->base_addr + offset);
    v32 = *(uint32_t*)(sdram->base_addr + offset);
    v64 = *(uint64_t*)(sdram->base_addr + offset);
    UNREF(v8); UNREF(v16); UNREF(v32); UNREF(v64);
    return STD_ERR_OK;
}

std_err_t mcu_sdram_test(mcu_sdram_t *sdram) {
    std_err_t err;
    err = mcu_sdram_test_aligns(sdram);                        if(err) return err;
    
    volatile uint16_t* SDRAM_BASE = ((volatile uint16_t*)sdram->base_addr);
    uint32_t ADDR_LINES = (sdram->hsdram.Init.RowBitsNumber == FMC_SDRAM_ROW_BITS_NUM_13) ? 13 : ((sdram->hsdram.Init.RowBitsNumber == FMC_SDRAM_ROW_BITS_NUM_12) ? 12 : 11);
    uint32_t DATA_LINES = (sdram->hsdram.Init.MemoryDataWidth == FMC_SDRAM_MEM_BUS_WIDTH_32) ? 32 : ((sdram->hsdram.Init.MemoryDataWidth == FMC_SDRAM_MEM_BUS_WIDTH_16) ? 16 : 8);
    uint32_t BANK_LINES = (sdram->hsdram.Init.InternalBankNumber == FMC_SDRAM_INTERN_BANKS_NUM_4) ? 2 : 1;
    err = test_addr_lines(SDRAM_BASE, ADDR_LINES);             if(err) return err;
    err = test_bank_lines(SDRAM_BASE, ADDR_LINES, BANK_LINES); if(err) return err;
    err = test_data_lines(SDRAM_BASE, DATA_LINES);             if(err) return err;
    return STD_ERR_OK;
}
